package com.binarysoft.service.common.snmp;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;

import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.ScopedPDU;
import org.snmp4j.Snmp;
import org.snmp4j.Target;
import org.snmp4j.UserTarget;
import org.snmp4j.asn1.BER;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.MessageProcessingModel;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.AuthMD5;
import org.snmp4j.security.AuthSHA;
import org.snmp4j.security.PrivAES128;
import org.snmp4j.security.PrivAES192;
import org.snmp4j.security.PrivAES256;
import org.snmp4j.security.PrivDES;
import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM;
import org.snmp4j.security.UsmUser;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.util.DefaultPDUFactory;

public class SnmpTool {

	public static SnmpResult collect(SnmpTarget snmpTarget, Snmp snmp, Collection<SnmpObject> objects, int groupSize) throws AbortException {
		// create result set
		SnmpResult snmpResult = new DefaultSnmpResult();

		Set<SnmpObject> type1Objects = new LinkedHashSet<SnmpObject>();
		Set<SnmpObject> type2Objects = new LinkedHashSet<SnmpObject>();
		Set<SnmpObject> type3Objects = new LinkedHashSet<SnmpObject>();
		// SNMP对象分组
		group(objects, type1Objects, type2Objects, type3Objects);
		// 采集标量对象
		type1collect(type1Objects, groupSize, snmpTarget, snmp, snmpResult);
		// 采集不带索引的向量对象
		type2collect(type2Objects, snmpTarget, snmp, snmpResult);
		// 采集带索引的向量对象
		type3collect(type3Objects, groupSize, snmpTarget, snmp, snmpResult);
		return snmpResult;
	}

	public static void type1collect(Collection<SnmpObject> objects, int groupSize, SnmpTarget snmpTarget, Snmp snmp, SnmpResult snmpResult) throws AbortException {

		PDU pdu = null;
		Target target = createTarget(snmp, snmpTarget);
		if (target.getVersion() == SnmpConstants.version3) {
			ScopedPDU pduV3 = new ScopedPDU();
			pduV3.setType(ScopedPDU.GET);
			if (snmpTarget.getContextName() != null) {
				pduV3.setContextName(new OctetString(snmpTarget.getContextName()));
			}
			pdu = pduV3;
		} else {
			pdu = DefaultPDUFactory.createPDU(target, PDU.GET);
		}

		int size = objects.size();
		int i = 0;
		// SNMP对象被分组采集，这个MAP就临时保存当前分组的对象,收到采集结果后，根据返回包中的OID
		// 能快速对应上SNMP对象,从而获取对象名称、对象索引值、对象值类型等信息
		Map<String, SnmpObject> group = new HashMap<String, SnmpObject>();
		Iterator<SnmpObject> iter = objects.iterator();
		while (iter.hasNext()) {
			SnmpObject snmpObject = iter.next();
			// OID + index
			StringBuffer path = new StringBuffer();
			path.append(snmpObject.getOid());
			// 检测是否启用DefaultIndexValue
			if (snmpObject.isUseDefaultIndex()) {
				path.append(".");
				path.append(snmpObject.getDefaultIndexValue());
			}
			pdu.add(new VariableBinding(new OID(path.toString())));
			group.put(path.toString(), snmpObject);
			i++;
			if (i == size || i % groupSize == 0) {
				get(pdu, target, snmp, group, snmpResult);
				group.clear();
				pdu.clear();
			}
		}
	}

	public static void type2collect(Collection<SnmpObject> objects, SnmpTarget snmpTarget, Snmp snmp, SnmpResult snmpResult) throws AbortException {

		PDU pdu = null;
		Target target = createTarget(snmp, snmpTarget);
		if (target.getVersion() == SnmpConstants.version3) {
			ScopedPDU pduV3 = new ScopedPDU();
			pduV3.setType(ScopedPDU.GETNEXT);
			if (snmpTarget.getContextName() != null) {
				pduV3.setContextName(new OctetString(snmpTarget.getContextName()));
			}
			pdu = pduV3;
		} else {
			pdu = DefaultPDUFactory.createPDU(target, PDU.GETNEXT);
		}

		Iterator<SnmpObject> iter = objects.iterator();
		while (iter.hasNext()) {
			SnmpObject snmpObject = iter.next();
			pdu.add(new VariableBinding(new OID(snmpObject.getOid())));
			boolean move_on = true;
			while (move_on) {
				// 返回false时表明当前SNMP对象的所有向量值采集完毕或者出错不能继续getNext
				move_on = getNext(pdu, target, snmp, snmpObject, snmpResult);
			}
			pdu.clear();
		}
	}

	public static void type3collect(Collection<SnmpObject> objects, int groupSize, SnmpTarget snmpTarget, Snmp snmp, SnmpResult snmpResult) throws AbortException {

		Set<SnmpObject> newObjects = new LinkedHashSet<SnmpObject>();
		Iterator<SnmpObject> iter = objects.iterator();
		while (iter.hasNext()) {
			SnmpObject snmpObject = iter.next();
			if (snmpObject.getIndexObject() != null) {
				// 从结果集中查找索引对象的值(可能包含多个值)
				// Collection<SnmpValue> values =
				// snmpResult.getValue(snmpObject.getIndexObject().getName());
				// 从结果集中查找索引对象的索引值(可能包含多个索引值)
				// 改进日期: 2008.12.19
				// 改进目的: 使用索引对象的索引值(可能包含多个索引值)作为SUB OID, 而不是使用索引对象的值
				// 改进的好处: 减少处理次数和网络传输次数，支持单级索引和多级索引，对于具备多级索引的SNMP对象，在这个方案下
				// 只需要采集任何一个索引就可以了，而不用采集所有索引对象的值
				// Collection<String> values =
				// snmpResult.getIndexes(snmpObject.getIndexObject().getName());
				Collection<String> values = snmpResult.getIndexes(snmpObject.getIndexObject().getOid());
				Iterator<String> vIt = values.iterator();
				while (vIt.hasNext()) {
					// 索引对象的索引值构成sub OID
					String subOID = vIt.next();
					// 从而把带索引的向量对象分解为标量对象
					SnmpObject newObject = snmpObject.clone(subOID, SnmpService.SINGLE_INSTANCE_OBJECT);
					newObjects.add(newObject);
				}
			}
		}
		// 最后按照采集标量对象的方法进行采集
		type1collect(newObjects, groupSize, snmpTarget, snmp, snmpResult);
	}

	public static void group(Collection<SnmpObject> objects, Collection<SnmpObject> type1, Collection<SnmpObject> type2, Collection<SnmpObject> type3) {
		Iterator<SnmpObject> iter = objects.iterator();
		while (iter.hasNext()) {
			SnmpObject snmpObject = iter.next();
			if (snmpObject.getObjectType() == SnmpService.SINGLE_INSTANCE_OBJECT) {
				type1.add(snmpObject);
			} else if (snmpObject.getObjectType() == SnmpService.MULTI_INSTANCE_OBJECT_WITHOUT_INDEX) {
				type2.add(snmpObject);
			} else if (snmpObject.getObjectType() == SnmpService.MULTI_INSTANCE_OBJECT_WITH_INDEX) {
				if (snmpObject.getIndexObject() != null) {
					// 索引对象置于类型2的集合中，优先采集
					type2.add(snmpObject.getIndexObject());
					type3.add(snmpObject);
				} else {
					type2.add(snmpObject);
				}
			} else {
				type2.add(snmpObject);
			}
		}
	}

	public static void get(PDU pdu, Target agent, Snmp snmp, Map<String, SnmpObject> group, SnmpResult snmpResult) throws AbortException {
		ResponseEvent response = null;
		try {
			response = snmp.send(pdu, agent);
		} catch (IOException e) {
			throw new AbortException(e);
		}
		if (response == null || response.getResponse() == null) {
			throw new AbortException("SNMP request timed out");
		}
		if (response != null) {
			PDU responsePDU = response.getResponse();
			if (responsePDU.getErrorStatus() == PDU.noError) {
				int reponseGroupSize = responsePDU.size();
				for (int k = 0; k < reponseGroupSize; k++) {
					VariableBinding vb = responsePDU.get(k);
					SnmpObject snmpObject = group.get(vb.getOid().toString());
					if (snmpObject == null) {
						System.out.println("---Invalid response OID detected---");
						System.out.println("Invalid response OID=" + vb.getOid().toString());
						System.out.println("Request PDU group size=" + group.size());
						System.out.println("Response PDU group size=" + reponseGroupSize);
						System.out.println("Request PDU group:");
						Set<Map.Entry<String, SnmpObject>> entries = group.entrySet();
						for (Map.Entry<String, SnmpObject> entry : entries) {
							String key = entry.getKey();
							SnmpObject so = entry.getValue();
							System.out.println("Key=" + key);
							System.out.println("Value=" + so);
						}
						System.out.println("---Invalid response OID will be ignored---");
						continue;
					}
					SnmpValue snmpValue = new SnmpValue();
					snmpValue.setObjectName(snmpObject.getName());
					// snmpValue.setOid(vb.getOid().toString());
					snmpValue.setOid(snmpObject.getOid());
					snmpValue.setIndex(snmpObject.getDefaultIndexValue());
					snmpValue.setTimestamp(System.currentTimeMillis());
					snmpValue.setValueType(snmpObject.getValueType());
					assignValue(snmpObject, snmpValue, vb);
					snmpResult.addValue(snmpValue);
				}
			} else {
				System.out.println(agent.getAddress() + " SNMP Error:" + responsePDU.getErrorStatusText());
				StringBuffer sb = new StringBuffer();
				Vector<VariableBinding> vec = pdu.getVariableBindings();
				for (int i = 0; i < vec.size(); i++) {
					VariableBinding vb = vec.get(i);
					System.out.println(vb.toString());
				}
			}
		}
	}

	private static void assignValue(SnmpObject snmpObject, SnmpValue snmpValue, VariableBinding vb) {
		if (vb.getVariable().isException() || vb.getVariable().getSyntax() == BER.ASN_NULL) {
			snmpValue.setValue(null);
		} else {
			if (snmpObject.getValueType() == SnmpService.TYPE_LONG) {
				try {
					snmpValue.setValue(vb.getVariable().toLong());
				} catch (Exception e) {
					snmpValue.setValue(SnmpService.DEFAULT_LONG_VALUE);
				}
			} else {
				// snmpValue.setValue(convertString(vb.getVariable().toString(),
				// snmpObject.getCharSet()));
				snmpValue.setValue(vb.getVariable().toString());
			}
		}
	}

	public static boolean getNext(PDU pdu, Target agent, Snmp snmp, SnmpObject snmpObject, SnmpResult snmpResult) throws AbortException {
		ResponseEvent response = null;
		try {
			response = snmp.send(pdu, agent);
		} catch (IOException e) {
			throw new AbortException(e);
		}
		if (response == null || response.getResponse() == null) {
			throw new AbortException("SNMP request timed out");
		}
		PDU responsePDU = response.getResponse();
		if (responsePDU.getErrorStatus() != PDU.noError) {
			System.out.println("SNMP Error:" + responsePDU.getErrorStatusText());
			// 采集出错，SNMP getNext无需继续
			return false;
		}
		if (responsePDU.size() <= 0) {
			// 采集无返回值, SNMP getNext无需继续
			return false;
		}
		OID responseOID = responsePDU.get(0).getOid();
		if (pdu.get(0).getOid().equals(responseOID)) {
			// 返回值的OID不能和前一次发送的OID相同，否则陷入无穷循环
			return false;
		}
		OID rootOID = new OID(snmpObject.getOid());
		if (!responseOID.startsWith(rootOID)) {
			// 返回值的OID不包含当前SNMP对象的OID,说明当前SNMP对象的所有向量值已经采集完毕,无需继续
			return false;
		}
		VariableBinding vb = responsePDU.get(0);
		String index = responseOID.toString().substring(snmpObject.getOid().length() + 1);
		SnmpValue snmpValue = new SnmpValue();
		snmpValue.setObjectName(snmpObject.getName());
		// snmpValue.setOid(vb.getOid().toString());
		snmpValue.setOid(snmpObject.getOid());
		snmpValue.setIndex(index);
		snmpValue.setTimestamp(System.currentTimeMillis());
		snmpValue.setValueType(snmpObject.getValueType());
		assignValue(snmpObject, snmpValue, vb);
		snmpResult.addValue(snmpValue);
		// 根据本次采集的结果构造下一个需要采集的OID
		pdu.clear();
		pdu.add(new VariableBinding(responseOID));
		return true;
	}

	public static void preprocess(Collection<SnmpObject> objectList, SnmpTarget target) {
		Set<SnmpObject> newObjects = new HashSet<SnmpObject>();
		Iterator<SnmpObject> iter = objectList.iterator();
		while (iter.hasNext()) {
			SnmpObject snmpObject = iter.next();
			snmpObject.setCharSet(target.getCharSet());
			// 添加索引对象
			if (snmpObject.getIndexObject() != null) {
				newObjects.add(snmpObject.getIndexObject());
			}
			// 为标量对象添加默认索引
			// if (snmpObject.getObjectType() ==
			// SnmpService.SINGLE_INSTANCE_OBJECT) {
			// if (!snmpObject.getOid().endsWith(SnmpService.DEFAULT_SUB_OID)) {
			// SnmpObject newSnmpObject =
			// snmpObject.clone(SnmpService.DEFAULT_INDEX);
			// newObjects.add(newSnmpObject);
			// iter.remove();
			// }
			// }
		}
		objectList.addAll(newObjects);
	}

	public static Target createTarget(Snmp snmp, SnmpTarget snmpTarget) {
		Target agent = null;
		if (snmpTarget.getSnmpVersion() == SnmpService.VERSION3) {

			UserTarget userTarget = new UserTarget();

			userTarget.setVersion(snmpTarget.getSnmpVersion());
			userTarget.setSecurityLevel(snmpTarget.getSecurityLevel());

			OctetString userName = new OctetString(snmpTarget.getUserName());
			userTarget.setSecurityName(userName);

			// 设置USM-用户安全模式 Added on 2009-06-16
			MPv3 mpv3 = (MPv3) snmp.getMessageProcessingModel(MessageProcessingModel.MPv3);
			USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(mpv3.createLocalEngineID()), 0);
			SecurityModels.getInstance().addSecurityModel(usm);
			UsmUser usmUser = null;

			if (SnmpService.AUTH_NOPRIV == snmpTarget.getSecurityLevel()) {
				OID authProto = getAuthProtocol(snmpTarget.getAuthProtocol());
				OctetString authPass = new OctetString(snmpTarget.getAuthPassword());
				usmUser = new UsmUser(userName, authProto, authPass, null, null);
			} else if (SnmpService.AUTH_PRIV == snmpTarget.getSecurityLevel()) {
				OID authProto = getAuthProtocol(snmpTarget.getAuthProtocol());
				OctetString authPass = new OctetString(snmpTarget.getAuthPassword());
				OID privProto = getPrivProtocol(snmpTarget.getPrivProtocol());
				OctetString privPass = new OctetString(snmpTarget.getPrivatePassword());
				usmUser = new UsmUser(userName, authProto, authPass, privProto, privPass);
			} else if (SnmpService.NOAUTH_NOPRIV == snmpTarget.getSecurityLevel()) {
				// to do nothing.
				usmUser = new UsmUser(userName, null, null, null, null);
			}
			// 设置用户
			snmp.getUSM().addUser(userName, usmUser);
			agent = userTarget;
		} else {
			CommunityTarget communityTarget = new CommunityTarget();
			communityTarget.setVersion(snmpTarget.getSnmpVersion());
			communityTarget.setCommunity(new OctetString(snmpTarget.getReadCommunity()));
			agent = communityTarget;
		}
		agent.setRetries(SnmpServiceBean.snmp_retry_count);
		agent.setTimeout(SnmpServiceBean.snmp_timeout);
		agent.setAddress(GenericAddress.parse("udp:" + snmpTarget.getHostAddress() + "/" + snmpTarget.getSnmpPort()));
		return agent;
	}

	public static OID getAuthProtocol(int authProtocol) {
		OID auth = AuthMD5.ID;
		if (authProtocol == SnmpService.AUTH_MD5) {
			auth = AuthMD5.ID;
		} else if (authProtocol == SnmpService.AUTH_SHA) {
			auth = AuthSHA.ID;
		}
		return auth;
	}

	public static OID getPrivProtocol(int privProtocol) {
		OID priv = PrivDES.ID;
		if (privProtocol == SnmpService.PRIV_DES) {
			priv = PrivDES.ID;
		} else if (privProtocol == SnmpService.PRIV_AES128) {
			priv = PrivAES128.ID;
		} else if (privProtocol == SnmpService.PRIV_AES192) {
			priv = PrivAES192.ID;
		} else if (privProtocol == SnmpService.PRIV_AES256) {
			priv = PrivAES256.ID;
		}
		return priv;
	}

	public static String convertString(String src, String charsetName) {
		try {
			byte[] b = fromString(src, ':', 16);
			if (b.length <= 1) {
				return src;
			}
			return new String(b, charsetName);
		} catch (Exception e) {
			return src;
		}
	}

	public static byte[] fromString(String string, char delimiter, int radix) {
		String delim = "";
		delim += delimiter;
		StringTokenizer st = new StringTokenizer(string, delim);
		byte[] value = new byte[st.countTokens()];
		for (int n = 0; st.hasMoreTokens(); n++) {
			String s = st.nextToken();
			value[n] = (byte) Integer.parseInt(s, radix);
		}
		return value;
	}
}
